<template>
  <!-- ActivitySign 签名 -->
  <PageContainer>
    <view class="container">
      <!-- <Header v-model="headerHeight" title="签名" /> -->
      <view class="wrapper">
        <view class="handCenter">
          <view class="background" :style="{ opacity: showSignDes ? 1 : 0 }">
            <text class="ali-icon">&#xe8cc;</text>
            <text>请在当前区域内签名</text>
          </view>
          <canvas
            class="handWriting"
            id="handWriting"
            :disable-scroll="true"
            @touchstart="(e: any) => uploadScaleStart(e)"
            @touchmove="(e: any) => uploadScaleMove(e)"
            @touchend="(e: any) => uploadScaleEnd(e)"
            canvas-id="handWriting"
          ></canvas>
        </view>
        <view class="handBtn">
          <button @click="retDraw" class="handBtn-item subBtn">重写</button>
          <button @click="uploadSign" :disabled="uploadLoading" class="handBtn-item uploadBtn">
            提交
          </button>
        </view>
      </view>
    </view>
  </PageContainer>
</template>

<script lang="ts" setup>
  /**  ActivitySign  签名 */
  import PageContainer from '@/components/PageContainer/index.vue';
  import { uploadImgFiles } from '@/api';
  import { onLoad, redirectTo } from '@/router';
  import { base64ToPath, Toast } from '@/utils';
  import { logManager } from '@/utils/log';
  import { reactive, ref } from 'vue';
  import type { UniAppTouchEvent, ctxLine, IPoint, IBethelPoint } from './types';
  import type { ActivitySign } from '@/router/types/modules';

  const headerHeight = ref(0);
  const showSignDes = ref(true);

  const param = ref<ActivitySign>();
  let success: (res: { tmpUrl: string; imgUrl: string }) => void;
  /** 初始变量 */
  const canvasName = ref('handWriting');
  const canvasContext = ref<WechatMiniprogram.CanvasContext>();
  const canvasWidth = ref();
  const canvasHeight = ref();
  /** 画布 历史绘制 */
  const history = ref<Array<ctxLine[]>>([]);
  /** 顺滑度，用60的距离来计算速度 */
  const smoothness = 60;
  /** 默认压力 */
  const pressure = 1;
  /** 最小笔画半径 */
  const lineMin = 0.5;
  /** 最大笔画半径 */
  const lineMax = 4;
  /** 笔迹倍数 */
  const lineSize = 1.5;

  onLoad<'ActivitySign'>(async (op) => {
    const { screenWidth } = uni.getWindowInfo()
    const { deviceOrientation } = uni.getSystemSetting()
    if (deviceOrientation === 'portrait' && screenWidth < 1000) {
      // 判断是否是折叠屏(未展开状态)
      uni.showModal({
        title: '提示',
        content: '请开启"自动旋转"并横置手机或展开折叠屏',
        showCancel: false,
      });
    }
    param.value = op;
    canvasContext.value = wx.createCanvasContext(canvasName.value);
    await getCanvasSize();
    setCanvasBg('rgba(255,255,255,0)');
  });

  /** 实时获取画布尺寸 */
  async function getCanvasSize() {
    return new Promise((resolve) => {
      var query = uni.createSelectorQuery();
      query
        .select('.handCenter')
        .boundingClientRect((res) => {
          const { width, height } = res as UniApp.NodeInfo;
          width && (canvasWidth.value = Math.ceil(width));
          height && (canvasHeight.value = Math.ceil(height));
          resolve(res as UniApp.NodeInfo);
        })
        .exec();
    });
  }

  /** 是否第一次触发 */
  const firstTouch = ref(true);
  const transparent = ref(1);
  const lineColor = ref('#1A1A1A');
  /** 画圆的半径 */
  const radius = ref(1);
  /** 当前线条 */
  const currentLine = ref<ctxLine[]>([]);
  const currentPoint = reactive<IPoint>({
    x: 0,
    y: 0,
    r: 0,
  });
  const cutArea = reactive({
    top: 0,
    right: 0,
    bottom: 0,
    left: 0,
  });

  const nowMouseType = ref('');
  /** 笔迹开始 */
  const uploadScaleStart = (e: UniAppTouchEvent) => {
    if (e.type != 'touchstart') return false;
    showSignDes.value = false;
    nowMouseType.value = e.type;
    let ctx = canvasContext.value;
    ctx?.setFillStyle(lineColor.value); // 初始线条设置颜色
    ctx?.setGlobalAlpha(transparent.value); // 设置半透明
    const { x, y } = e.touches[0];
    currentLine.value.unshift({
      time: new Date().getTime(),
      dis: 0,
      x,
      y,
      r: 0,
    });
    currentPoint.x = x;
    currentPoint.y = y;
    if (firstTouch.value) {
      cutArea.top = y;
      cutArea.right = x;
      cutArea.bottom = y;
      cutArea.left = x;
      firstTouch.value = false;
    }
    pointToLine();
  };

  /** 笔迹移动 */
  const uploadScaleMove = async (e: UniAppTouchEvent) => {
    if (e.type != 'touchmove') return false;

    nowMouseType.value = e.type;
    if (e.cancelable) {
      // 判断默认行为是否已经被禁用
      if (!e.defaultPrevented) {
        e.preventDefault();
      }
    }
    let point = {
      x: e.touches[0].x,
      y: e.touches[0].y,
    };

    //测试裁剪
    if (point.y < cutArea.top) {
      cutArea.top = point.y;
    }
    if (point.y < 0) cutArea.top = 0;

    if (point.x > cutArea.right) {
      cutArea.right = point.x;
    }
    if (canvasWidth.value - point.x <= 0) {
      cutArea.right = canvasWidth.value;
    }
    if (point.y > cutArea.bottom) {
      cutArea.bottom = point.y;
    }
    if (canvasHeight.value - point.y <= 0) {
      cutArea.bottom = canvasHeight.value;
    }
    if (point.x < cutArea.left) {
      cutArea.left = point.x;
    }
    if (point.x < 0) cutArea.left = 0;

    setLastPoint();
    currentPoint.x = point.x;
    currentPoint.y = point.y;

    currentLine.value.unshift({
      time: new Date().getTime(),
      dis: distance(currentPoint, lastPoint),
      x: point.x,
      y: point.y,
      r: 0,
    });
    pointToLine();
  };

  /** 笔迹结束 */
  const uploadScaleEnd = (e: UniAppTouchEvent) => {
    if (e.type != 'touchend') return 0;
    nowMouseType.value = e.type;
    let point = {
      x: e.changedTouches[0].x,
      y: e.changedTouches[0].y,
    };

    setLastPoint();

    currentPoint.x = point.x;
    currentPoint.y = point.y;

    const pData: ctxLine = {
      time: new Date().getTime(),
      dis: distance(currentPoint, lastPoint),
      x: point.x,
      y: point.y,
      r: 0,
    };
    console.log(currentLine.value);

    currentLine.value.unshift(pData);
    getImageData(currentLine.value);

    pointToLine();
    currentLine.value = [];
  };

  function calcBethelLine(line: ctxLine[], lineColor: string) {
    if (line.length <= 1) {
      line[0].r = radius.value;
      return;
    }

    let x0,
      x1,
      x2,
      y0,
      y1,
      y2,
      r0,
      r1,
      r2,
      len,
      lastRadius,
      dis = 0,
      time = 0,
      curveValue = 0.5;
    if (line.length <= 2) {
      x0 = line[1].x;
      y0 = line[1].y;
      x2 = line[1].x + (line[0].x - line[1].x) * curveValue;
      y2 = line[1].y + (line[0].y - line[1].y) * curveValue;
      x1 = x0 + (x2 - x0) * curveValue;
      y1 = y0 + (y2 - y0) * curveValue;
    } else {
      x0 = line[2].x + (line[1].x - line[2].x) * curveValue;
      y0 = line[2].y + (line[1].y - line[2].y) * curveValue;
      x1 = line[1].x;
      y1 = line[1].y;
      x2 = x1 + (line[0].x - x1) * curveValue;
      y2 = y1 + (line[0].y - y1) * curveValue;
    }
    /** 从计算公式看，
     * 三个点分别是(x0,y0),(x1,y1),(x2,y2)(x1,y1)这个是控制点，
     * 控制点不会落在曲线上；
     * 实际上，这个点还会手写获取的实际点，却落在曲线上
     */

    len = distance(
      {
        x: x2,
        y: y2,
        r: 0,
      },
      {
        x: x0,
        y: y0,
        r: 0,
      },
    );
    lastRadius = radius.value;
    for (let n = 0; n < line.length - 1; n++) {
      dis += line[n].dis;
      time += line[n].time - line[n + 1].time;
      if (dis > smoothness) break;
    }

    radius.value = Math.min((time / len) * pressure + lineMin, lineMax) * lineSize;
    line[0].r = radius.value;

    /** 计算笔迹半径 */
    if (line.length <= 2) {
      r0 = (lastRadius + radius.value) / 2;
      r1 = r0;
      r2 = r1;
    } else {
      r0 = (line[2].r + line[1].r) / 2;
      r1 = line[1].r;
      r2 = (line[1].r + line[0].r) / 2;
    }
    let n = 5;
    let point: IPoint[] = [];
    for (let i = 0; i < n; i++) {
      let t = i / (n - 1);
      let x = (1 - t) * (1 - t) * x0 + 2 * t * (1 - t) * x1 + t * t * x2;
      let y = (1 - t) * (1 - t) * y0 + 2 * t * (1 - t) * y1 + t * t * y2;
      let r = lastRadius + ((radius.value - lastRadius) / n) * i;
      point.push({
        x: x,
        y: y,
        r: r,
      });
      if (point.length == 3) {
        let bPoints: IBethelPoint[] = ctaCalc(
          point[0].x,
          point[0].y,
          point[0].r,
          point[1].x,
          point[1].y,
          point[1].r,
          point[2].x,
          point[2].y,
          point[2].r,
        );
        bPoints[0].color = lineColor;
        bethelDraw(bPoints);
        point = [
          {
            x: x,
            y: y,
            r: r,
          },
        ];
      }
    }

    currentLine.value = line;
  }

  function ctaCalc(
    x0: number,
    y0: number,
    r0: number,
    x1: number,
    y1: number,
    r1: number,
    x2: number,
    y2: number,
    r2: number,
  ) {
    let a: IBethelPoint[] = [],
      vx01,
      vy01,
      norm,
      n_x0,
      n_y0,
      vx21,
      vy21,
      n_x2,
      n_y2;
    vx01 = x1 - x0;
    vy01 = y1 - y0;
    norm = Math.sqrt(vx01 * vx01 + vy01 * vy01 + 0.0001) * 2;
    vx01 = (vx01 / norm) * r0;
    vy01 = (vy01 / norm) * r0;
    n_x0 = vy01;
    n_y0 = -vx01;
    vx21 = x1 - x2;
    vy21 = y1 - y2;
    norm = Math.sqrt(vx21 * vx21 + vy21 * vy21 + 0.0001) * 2;
    vx21 = (vx21 / norm) * r2;
    vy21 = (vy21 / norm) * r2;
    n_x2 = -vy21;
    n_y2 = vx21;
    a.push({
      mx: x0 + n_x0,
      my: y0 + n_y0,
      color: '#1A1A1A',
    });
    a.push({
      c1x: x1 + n_x0,
      c1y: y1 + n_y0,
      c2x: x1 + n_x2,
      c2y: y1 + n_y2,
      ex: x2 + n_x2,
      ey: y2 + n_y2,
    });
    a.push({
      c1x: x2 + n_x2 - vx21,
      c1y: y2 + n_y2 - vy21,
      c2x: x2 - n_x2 - vx21,
      c2y: y2 - n_y2 - vy21,
      ex: x2 - n_x2,
      ey: y2 - n_y2,
    });
    a.push({
      c1x: x1 - n_x2,
      c1y: y1 - n_y2,
      c2x: x1 - n_x0,
      c2y: y1 - n_y0,
      ex: x0 - n_x0,
      ey: y0 - n_y0,
    });
    a.push({
      c1x: x0 - n_x0 - vx01,
      c1y: y0 - n_y0 - vy01,
      c2x: x0 + n_x0 - vx01,
      c2y: y0 + n_y0 - vy01,
      ex: x0 + n_x0,
      ey: y0 + n_y0,
    });
    let one = a[0];
    if (one.mx) one.mx = calcPoint(one.mx);

    if (one.my) one.my = calcPoint(one.my);

    for (let i = 1; i < a.length; i++) {
      let v = a[i];
      if (v.c1x) v.c1x = calcPoint(v.c1x);
      if (v.c1y) v.c1y = calcPoint(v.c1y);
      if (v.c2x) v.c2x = calcPoint(v.c2x);
      if (v.c2y) v.c2y = calcPoint(v.c2y);
      if (v.ex) v.ex = calcPoint(v.ex);
      if (v.ey) v.ey = calcPoint(v.ey);
    }
    return a;
  }

  /** 计算点 */
  function calcPoint(num: number, fix: number = 1) {
    return parseFloat(num.toFixed(fix));
  }

  /** 求两点之间距离 */
  function distance(a: IPoint, b: IPoint): number {
    let x = b.x - a.x;
    let y = b.y - a.y;
    return Math.sqrt(x * x + y * y);
  }

  const lastPoint = reactive<IPoint>({
    x: 0,
    y: 0,
    r: 0,
  });
  const setLastPoint = () => {
    lastPoint.x = currentPoint.x;
    lastPoint.y = currentPoint.y;
    lastPoint.r = currentPoint.r;
  };

  /** 画两点之间的线条；参数为:line，会绘制最近的开始的两个点； */
  function pointToLine() {
    calcBethelLine(currentLine.value, lineColor.value);
    return;
  }

  function bethelDraw(point: IBethelPoint[]) {
    let ctx = canvasContext.value;
    ctx?.beginPath();
    let one = point[0];
    if (one.mx && one.my) ctx?.moveTo(one.mx, one.my);
    if (one.color) {
      ctx?.setFillStyle(one.color);
      ctx?.setStrokeStyle(one.color);
    }
    for (let i = 1; i < point.length; i++) {
      let { c1x, c1y, c2x, c2y, ex, ey } = point[i];
      if (
        c1x != undefined &&
        c1y != undefined &&
        c2x != undefined &&
        c2y != undefined &&
        ex != undefined &&
        ey
      ) {
        ctx?.bezierCurveTo(c1x, c1y, c2x, c2y, ex, ey);
      }
    }

    ctx?.stroke();
    ctx?.fill();
    ctx?.draw(true);
  }

  /**
   * 设置canvas背景色  不设置  导出的canvas的背景为透明
   */
  async function setCanvasBg(color: string) {
    /* 将canvas背景设置为 白底，不设置  导出的canvas的背景为透明 */
    //rect() 参数说明  矩形路径左上角的横坐标，左上角的纵坐标, 矩形路径的宽度, 矩形路径的高度
    //这里是 canvasHeight - 4 是因为下边盖住边框了，所以手动减了
    await getCanvasSize();
    const ctx = canvasContext.value;
    ctx?.rect(0, 0, canvasWidth.value, canvasHeight.value - 4);
    ctx?.setFillStyle(color);
    ctx?.fill(); //设置填充
    ctx?.draw(); //开画
  }

  /** 获取画布快照 */
  function getImageData(line: ctxLine[]) {
    history.value.push(line);
    // const [width, height] = [canvasWidth.value, canvasHeight.value];
    // console.log(width, height)
    // wx.canvasGetImageData({
    //   canvasId: canvasName.value,
    //   x: 0,
    //   y: 0,
    //   width,
    //   height,
    //   success: ({ data }) => history.value.push(data),
    // });
  }

  /** 撤销绘制 */
  function withdraw() {
    if (history.value.length > 0) {
      // const [width, height] = [canvasWidth.value, canvasHeight.value];
      // const list = history.value;
      // list.pop();
      // let data = list[list.length - 1];
      // if (data) {
      //   wx.canvasPutImageData({
      //     canvasId: canvasName.value,
      //     x: 0,
      //     y: 0,
      //     width,
      //     data,
      //     success: (res) => console.log(res),
      //   });
      // } else {
      //   retDraw();
      // }
      // lineColor.value = '#fff';
      // history.value
      //   .pop()
      //   ?.reverse()
      //   .forEach((v) => {
      //     currentLine.value.unshift(v);
      //     pointToLine();
      //   });
      // currentLine.value = [];
      // lineColor.value = '#1A1A1A';
      // history.value.pop();
      // retDraw()
      // for (let i = 0; i < history.value.length; i++) {
      //   const v = JSON.parse(JSON.stringify(history.value[i])).reverse() as any[];
      //   v.forEach((item) => {
      //     currentLine.value.unshift(item);
      //     pointToLine();
      //   });
      //   currentLine.value = [];
      // }
    }
  }

  /** 重写 */
  async function retDraw() {
    await getCanvasSize();
    const ctx = canvasContext.value;
    ctx?.clearRect(0, 0, canvasWidth.value, canvasHeight.value);
    ctx?.draw();
    history.value = [];
    showSignDes.value = true;
    // setCanvasBg('#fff');
  }

  /** 上传签名 */
  const uploadLoading = ref(false);
  async function uploadSign() {
    if (uploadLoading.value) return;
    if (history.value.length === 0) return Toast('请先签名');
    if (history.value.length == 1) {
      const start = history.value[0][0];
      const end = history.value[0][history.value[0].length - 1];
      const distance = Math.hypot(end.x - start.x, end.y - start.y);
      if (distance < 30) {
        return Toast('未检测到有效签名，请重新签名');
      }
    }
    await getCanvasSize();
    const [width, height] = [canvasWidth.value, canvasHeight.value];
    uploadLoading.value = true;
    uni.canvasToTempFilePath({
      x: 0,
      y: 0,
      width,
      height,
      canvasId: canvasName.value,
      fileType: 'png',
      success: async (res) => {
        let path = res.tempFilePath;
        try {
          path = await rotateSign(res.tempFilePath);
        } catch (error) {
          logManager.error('旋转签名错误', error);
        }
        let file = { path } as TempFile;

        try {
          const r = await uploadImgFiles(file);
          redirectTo({
            name: 'signInResult',
            options: {
              activityId: param.value?.activityId as number,
              url: r.imagePathBig,
              equipmentNumber: param.value?.equipmentNumber as string,
              activityRegisterId: param.value?.activityRegisterId as number,
              signType: param.value?.signType,
            },
          });
        } catch (error) {
          uploadLoading.value = false;
          logManager.error(error);
        }
      },
      fail: (err) => {
        uni.showModal({
          title: '提示',
          content: '签名生成失败, 请联系管理员处理',
          showCancel: false,
        });
        uploadLoading.value = false;
        logManager.error(err);
      },
    });
  }

  /** 旋转签名 */
  async function rotateSign(url: string): Promise<string> {
    await getCanvasSize();
    const [w, h] = [canvasWidth.value, canvasHeight.value];
    const canvas = wx.createOffscreenCanvas({ type: '2d' }) as WechatMiniprogram.OffscreenCanvas & {
      toDataURL: (type: string, num: number) => string;
    };
    const ctx = canvas.getContext('2d');

    const image = canvas.createImage();
    await new Promise((resolve) => {
      image.onload = resolve;
      image.src = url;
    });
    /** 画布旋转代码 */
    // canvas.width = h;
    // canvas.height = w;
    // ctx.fillStyle = '#FFFFFF';
    // ctx.fillRect(0, 0, h, w);
    // ctx.translate(h / 2, w / 2);
    // ctx.rotate((-90 * Math.PI) / 180);
    // ctx.drawImage(image, -w / 2, -h / 2, w, h);
    /** 画布旋转代码 */
    // 2023/10/10 取消画布旋转
    console.log('canvas size', w, h);
    canvas.width = w;
    canvas.height = h;
    ctx.fillStyle = '#FFFFFF';
    ctx.fillRect(0, 0, w, h);
    ctx.drawImage(image, 0, 0, w, h);
    return await base64ToPath(canvas?.toDataURL('image/png', 1));
  }
</script>

<style lang="scss" scoped>
  page {
    background: #fbfbfb;
    height: auto;
    overflow: hidden;
  }

  .wrapper {
    height: calc(100vh - 20rpx);
    margin: 10rpx 20rpx;
    overflow: hidden;
    display: flex;
    align-content: center;
    flex-direction: row;
    justify-content: center;
    font-size: 28rpx;
  }

  .handWriting {
    width: 100%;
    height: 100%;
    position: absolute;
    left: 0;
    top: 0;
    z-index: 1;
  }

  .handCenter {
    border: 4rpx dashed #e9e9e9;
    flex: 1;
    overflow: hidden;
    box-sizing: border-box;
    margin-left: 4rpx;
    position: relative;
    background-color: #fff;
    .background {
      width: 100%;
      height: 100%;
      display: flex;
      align-items: center;
      justify-content: center;
      flex-direction: column;
      color: #e0e0dc;
      font-size: 32rpx;
      transition: opacity 0.3s;
      .ali-icon {
        font-size: 48rpx;
        margin-bottom: 24rpx;
      }
    }
  }

  .handBtn {
    // flex: 1;
    margin-left: 20rpx;
    display: flex;
    align-items: center;
    justify-content: space-around;
    flex-direction: column;
    .handBtn-item {
      font-size: 16rpx;
      color: #666;
      height: 100rpx;
      line-height: 1;
      display: flex;
      align-items: center;
      justify-content: center;
      &.uploadBtn {
        color: #fff;
        background-color: #2878ff;
      }
    }
    .left,
    .right {
      display: inline-flex;
      flex-direction: column;
      align-items: center;
      // flex: 1;
      > button {
        font-size: 28rpx;
        // transform: rotate(90deg);
        // margin: 30rpx 0;
        color: #666;
      }
    }
    .right {
      justify-content: flex-end;
    }
  }
</style>
